#pragma once


#include <atlmisc.h>

#include <list>

using namespace std;



template <class T>
class CAutoSnapImpl
{
protected:
	enum ESnapDirection
	{
		sdNone = 0,
		sdLeft = 1,
		sdTop = 2,
		sdRight = 4,
		sdBottom = 8
	}	;

	list<CPoint> lMoveHistory;
	int nHistoryLength;
	int nSnapLimit;
	bool bEnableSnap;

	int CanSnapNow(CPoint pt)
	{
		lMoveHistory.push_back(pt);
		list<CPoint>::size_type st = lMoveHistory.size();
		if((int)st > nHistoryLength)
		{
			for(int i = 0; i < (int)st - nHistoryLength; i++)
				lMoveHistory.pop_front();
		}
		T * pT = static_cast<T *>(this);
		CRect r, rwa;
		int nMoving;

		pT->GetWindowRect(&r);
		::SystemParametersInfo(SPI_GETWORKAREA, 0, &rwa, 0);
		nMoving = GetMoveDirection(lMoveHistory);

		if(nMoving == sdNone)
			return 0;
		
		int nResult = sdNone;
		int n;

		n = r.left - rwa.left;
		if(n > 0 && n < nSnapLimit && nMoving & sdLeft)
			nResult |= sdLeft;
		n = rwa.right - r.right;
		if(n > 0 && n < nSnapLimit && nMoving & sdRight)
			nResult |= sdRight;
		n = r.top - rwa.top;
		if(n > 0 && n < nSnapLimit && nMoving & sdTop)
			nResult |= sdTop;
		n = rwa.bottom - r.bottom;
		if(n > 0 && n < nSnapLimit && nMoving & sdBottom)
			nResult |= sdBottom;
		return nResult;
	}

	void SnapTo(int nWhere)
	{
		if(nWhere == 0)
			return;

		T * pT = static_cast<T *>(this);
		int w, h;
		CRect r, rwa;
		CPoint ptCursor;

		pT->GetWindowRect(&r);
		::SystemParametersInfo(SPI_GETWORKAREA, 0, &rwa, 0);
		::GetCursorPos(&ptCursor);

		w = r.Width();
		h = r.Height();

		if(nWhere & sdLeft)
		{
			ptCursor.x -= (r.left - rwa.left);
			r.left = rwa.left;
			r.right = r.left + w;
		}
		if(nWhere & sdRight)
		{
			ptCursor.x += (rwa.right - r.right);
			r.right = rwa.right;
			r.left = r.right - w;
		}
		if(nWhere & sdTop)
		{
			ptCursor.y -= (r.top - rwa.top);
			r.top = rwa.top;
			r.bottom = r.top + h;
		}
		if(nWhere & sdBottom)
		{
			ptCursor.y += (rwa.bottom - r.bottom);
			r.bottom = rwa.bottom;
			r.top = r.bottom - h;
		}

		::SetCursorPos(ptCursor.x, ptCursor.y);
		pT->MoveWindow(&r);
	}

	int GetMoveDirection(list<CPoint> & lps)
	{
		if((int)lps.size() < nHistoryLength)
			return sdNone;

		bool b;
		list<CPoint>::const_iterator it;
		CPoint pt1, pt2;
		int nResult = sdNone;

		// now try if move left
		b = true;
		it = lps.begin();
		pt1 = *it;
		it++;
		for(; it != lps.end(); it++)
		{
			pt2 = *it;
			if(pt2.x > pt1.x)
			{
				b = false;
				break;
			}
			pt1 = pt2;
		}
		if(b)
			nResult |= sdLeft;

		// now try if move right
		b = true;
		it = lps.begin();
		pt1 = *it;
		it++;
		for(; it != lps.end(); it++)
		{
			pt2 = *it;
			if(pt2.x < pt1.x)
			{
				b = false;
				break;
			}
			pt1 = pt2;
		}
		if(b)
			nResult |= sdRight;

		// now try if move top
		b = true;
		it = lps.begin();
		pt1 = *it;
		it++;
		for(; it != lps.end(); it++)
		{
			pt2 = *it;
			if(pt2.y > pt1.y)
			{
				b = false;
				break;
			}
			pt1 = pt2;
		}
		if(b)
			nResult |= sdTop;

		// now try if move bottom
		b = true;
		it = lps.begin();
		pt1 = *it;
		it++;
		for(; it != lps.end(); it++)
		{
			pt2 = *it;
			if(pt2.y < pt1.y)
			{
				b = false;
				break;
			}
			pt1 = pt2;
		}
		if(b)
			nResult |= sdBottom;
		return nResult;
	}
public:
	CAutoSnapImpl(void): nHistoryLength(4), nSnapLimit(12), bEnableSnap(true)
	{

	}

	~CAutoSnapImpl(void)
	{

	}

	BEGIN_MSG_MAP(CAutoSnap)
		MESSAGE_HANDLER(WM_MOVE, OnMove)
	END_MSG_MAP()

	bool IsEnableSnap(void){return bEnableSnap;};
	void EnableSnap(bool b = true){bEnableSnap = b;};

	LRESULT OnMove(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL & bHandled)
	{
		if(!bEnableSnap)
			return 0;

		CRect r;
		CPoint pt;

		T * pT = static_cast<T *>(this);
		pT->GetWindowRect(&r);
		pt.x = r.left;
		pt.y = r.top;
		SnapTo(CanSnapNow(pt));
		return 0;
	}
};
